﻿using System;
using System.IO;
using System.Runtime.Serialization;
using com.calitha.goldparser;
using com.calitha.commons;
using System.Collections.Generic;
using com.calitha.goldparser.sql;
using System.Data.SqlClient;




namespace com.calitha.goldparser.sql
{
    public class SQLParamExtract : com.calitha.goldparser.sql.MyParser
    {

        public string OldSql { get; set; }
        public string NewSql { get; private set; }
        public bool TransformComppleteCorrectly { get; private set; }
        public Replacements Replacements = new Replacements();
        private UniqueIDGenerator IdGenerator = new UniqueIDGenerator();


        public SQLParamExtract(string filename)
            : base(filename)
        { }

        public SQLParamExtract(string baseName, string resourceName)
            : base(baseName, resourceName)
        { }

        public SQLParamExtract(Stream stream)
            : base(stream)
        { }

        private new void AcceptEvent(LALRParser parser, AcceptEventArgs args)
        {
            
            TransformStatements((Statements)args.Token.UserObject);
            TransformComppleteCorrectly = true;
        }
        public new void Parse(string sql)
        {
            TransformComppleteCorrectly = false;
            this.parser.OnAccept += new LALRParser.AcceptHandler(this.AcceptEvent);
            OldSql = sql;
            base.Parse(sql);
        }

        private void TokenErrorEvent(LALRParser parser, TokenErrorEventArgs args)
        {
            throw new ParserException("TokenErrorEvent in token: " + args.Token.ToString());
        }

        private void ParseErrorEvent(LALRParser parser, ParseErrorEventArgs args)
        {
            throw new ParserException("ParseErrorEvent, expected: " + args.ExpectedTokens.ToString());
        }

        private void TransformStatements(Statements statements)
        {
            //Insert case
            foreach (InsertStm insert in statements.ListInserts)
            {
                TransformInsert(insert);
            }
            foreach (SelectStm select in statements.ListSelect)
            {
                TransformSelect(select);
            }
            foreach (UpdateStm update in statements.ListUpdate)
            {
                TransformUpdate(update);
            }
            foreach (DeleteStm delete in statements.ListDelete)
            {
                TransformDelete(delete);
            }
            NewSql = Replacements.Transform(OldSql);
        }
        private void TransformInsert(InsertStm insert)
        {
            //Example
            //insert into table (columA, ColumB, ColumC) values (1+idA - 4, 'hola',5.5)
            //convert to:
            //string query="insert into table (ColumA, ColumB, ColumC) values (@paramColumA + idA - @paramColumA1, @paramColumB, @paramColumC)d
            //SqlParam paramColumA=new (SqlParam("@paramColumA",1));
            //SqlParameter paramColumA = new SqlParameter("@paramColumA", 1);
            //...
            IdMember idMember;
            Expression expresion;
            while (insert.IdList.Count > 0)
            {
                idMember = insert.IdList.Dequeue();
                expresion = insert.ExprList.Dequeue();
                Queue<string> id; 
                foreach (PredExp predExp in expresion)
                {
                    id = new Queue<string>();
                    id.Enqueue(idMember.Id.Text);
                    TransformPredExp(predExp, id);
                }
            }
        }
        private void TransformSelect(SelectStm select)
        {
            foreach (PredExp exp in select.WhereExpresion) //we have to evaluate all expresion
            //expresion is, remember: exp and exp or exp
            {
                TransformPredExp(exp);
            }
            foreach (PredExp exp in select.HavingExpresion) //we have to evaluate all expresion
            //expresion is, remember: exp and exp or exp
            {
                TransformPredExp(exp);
            }
            
        }
        private void TransformUpdate(UpdateStm update)
        {
            foreach (Assign assign in update.AssignList)
            {
                foreach (PredExp exp in assign.Expression)
                {
                    //id of assignlist is prioritary
                    exp.ListIDs.Insert(0, assign.Id);
                    TransformPredExp(exp);
                }
            }
            foreach (PredExp prExp in update.WhereExpresion) 
            {
                TransformPredExp(prExp);
            }
        }
        private void TransformDelete(DeleteStm delete)
        {
            foreach (PredExp prExp in delete.Expression)
            {
                TransformPredExp(prExp);
            }            

        }
        /// <summary>
        /// Transform a predExp with a prioritary id list
        /// </summary>
        /// <param name="predExp"></param>
        /// <param name="idList"></param>
        public void TransformPredExp(PredExp predExp, Queue<string> idList)
        {

            //we are going to replace integer, reals, strings...
            string id = "param"; //if we not have id, default id is paramX
            Replacement replacement;
            foreach (TerminalToken token in predExp.ListIntegers)
            {
                if (idList.Count > 0) id = idList.Dequeue();
                replacement = new Replacement(IdGenerator.generateId(id), token);
                Replacements.Add(replacement);
            }
            foreach (TerminalToken token in predExp.ListReals)
            {
                if (idList.Count > 0) id = idList.Dequeue();
                replacement = new Replacement(IdGenerator.generateId(id), token);
                Replacements.Add(replacement);
            }
            foreach (TerminalToken token in predExp.ListStrings)
            {
                if (idList.Count > 0) id = idList.Dequeue();
                replacement = new Replacement(IdGenerator.generateId(id), token);
                Replacements.Add(replacement);
            }
            //now, Tuples
            TransformStatements(predExp.Tuple.StatementTuple);
            
            foreach (Expression exp in predExp.Tuple.ExpresionListTuple)
            {
                foreach (PredExp pred in exp)
                {
                    Queue<string> newListId = new Queue<string>();
                    foreach (TerminalToken newid in pred.ListIDs)
                    {
                        newListId.Enqueue(newid.Text);
                    }
                    TransformPredExp(pred, newListId);
                }
            }
        }
        /// <summary>
        /// Transofrm a predExp without a prioritary id list (for "where" for example)
        /// </summary>
        /// <param name="predExp"></param>
        public void TransformPredExp(PredExp predExp)
        {
            Queue<string> idList = new Queue<string>();
            foreach (TerminalToken id in predExp.ListIDs)
            {
                idList.Enqueue(id.Text);
            }
            TransformPredExp(predExp, idList);
        }
    }
    /// <summary>
    /// Class for generate Unique ID for variables
    /// </summary>
    internal class UniqueIDGenerator
    {
        private Dictionary<string, int> usedIds = new Dictionary<string, int>();
        /// <summary>
        /// Generate a unique id for a name. Strange caracteres will be replaced by "_"
        /// </summary>
        /// <param name="name">the name of the original id</param>
        /// <returns>a unique id </returns>
        public string generateId(string name)
        {
            string transformName = transform(name);
            if (usedIds.ContainsKey(transformName))
            {
                int id = usedIds[transformName] + 1;
                usedIds[transformName] = id;
                return transformName + "" + usedIds[transformName].ToString();
            }
            else
            {
                usedIds.Add(transformName, 0);
                return transformName;
            }
        }
        private string transform(string name)
        {
            name = "@" + name;
            name = name.Replace("[", "_").Replace("]", "_");
            name = name.Replace(".", "_");
            return name;
        }
    }
    /// <summary>
    /// This class represent a replacement in the sql query, the tokenForReplace will be replace by the new id
    /// </summary>
    public class Replacement : IComparable<Replacement>
    {
        public string newId;
        public TerminalToken tokenForReplace;
        public Replacement() { }
        public Replacement(string NewId, TerminalToken TokenForReplace)
        {
            newId = NewId;
            tokenForReplace = TokenForReplace;
        }
        public int Position
        {
            get { return tokenForReplace.Location.Position; }
        }
        public int LengthToken
        {
            get { return tokenForReplace.Text.Length; }
        }
        public int LengthId
        {
            get { return newId.Length; }
        }
        public string Text
        {
            get { return tokenForReplace.Text; }
        }

        #region IComparable<Replacement> Members

        public int CompareTo(Replacement other)
        {
            if (this.tokenForReplace.Location.Position < other.tokenForReplace.Location.Position) return -1;
            else if (this.tokenForReplace.Location.Position == other.tokenForReplace.Location.Position) return 0;
            else return 1;
        }

        #endregion
    }
    public class Replacements : List<Replacement>
    {
        /// <summary>
        /// Transform a string with Replacement entries.
        /// </summary>
        /// <param name="stringToTransform"></param>
        /// <returns></returns>
        public string Transform(string stringToTransform)
        {
            this.Sort(); //order for the position of the token
            if (this.Count == 0) return stringToTransform; //nothing to do
            int offset = 0;
            string result = stringToTransform;
            string leftstring;
            string rigthstring;
            int positionOffset;
            foreach (Replacement replacement in this)
            {
                positionOffset = replacement.Position + offset;

                leftstring = result.Substring(0, positionOffset);
                int rigthStart = positionOffset + replacement.LengthToken;
                if (rigthStart < result.Length)
                {
                    
                    int rigthLen = result.Length - rigthStart;
                    rigthstring = result.Substring(rigthStart, rigthLen);
                }
                else
                {
                    rigthstring = "";
                }
                result = leftstring + replacement.newId + rigthstring;
                offset += replacement.LengthId - replacement.LengthToken;

            }
            return result;
        }
    }


}

